package cn.xinfei.xdecision.manager.web.interceptor;

import cn.xinfei.xdecision.common.model.common.ResponseEntityBuilder;
import cn.xinfei.xdecision.common.model.common.ResponseEntityDto;
import cn.xinfei.xdecision.common.model.login.AccountSessionWrap;
import cn.xinfei.xdecision.common.model.login.SSOUser;
import cn.xinfei.xdecision.common.model.login.SessionManager;
import cn.xinfei.xdecision.common.utils.constant.Constants;
import cn.xinfei.xdecision.common.utils.constant.ServiceFilterConstant;
import cn.xinfei.xdecision.common.utils.exception.ApiException;
import cn.xinfei.xdecision.common.utils.exception.enums.ErrorCodeEnum;
import cn.xinfei.xdecision.common.utils.util.RequestUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import feign.Request;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Token 拦截器，验证 token 的有效性
 * 验证通过后，获得 {@link SSOUser} 信息，并加入到SessionManager
 */
@Component
@Slf4j
public class TokenAuthenticationInterceptor extends HandlerInterceptorAdapter {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${xdecision.sso.mockEnable:false}")
    private Boolean mockEnable;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        String ip = RequestUtil.getClientIP(request);
        AccountSessionWrap acsw = new AccountSessionWrap(ip, uri);
        String reqUuid = UUID.randomUUID().toString().replaceAll("-", "");
        String requestMethod = request.getMethod();
        StringBuffer requestURL = request.getRequestURL();
        acsw.setTraceId(reqUuid);
        String token = request.getHeader(Constants.SYSTEM_KEY_TOKEN);
        SessionManager.setSession(acsw);

        if (Request.HttpMethod.OPTIONS.name().equals(request.getMethod())) {
            log.info("OPTIONS请求，放行");
            return true;
        }
        log.info("requestURL===>{}", requestURL);
        log.info("requestMethod===>{}", requestMethod);
        log.info("uri===>{}", uri);
        if (ServiceFilterConstant.isSessionFilter(uri)) {
            return true;
        }

        //方便本地测试
        if (mockEnable) {
            acsw.setSsoUser(mockLoginUser());
            return true;
        }

        try {
            SSOUser ssoUser = buildLoginUserByToken(response, token);
            acsw.setSsoUser(ssoUser);
        } catch (ApiException e1) {
            output(response, e1.errCode, e1.getMessage());
            return false;
        } catch (Exception e) {
            log.error("调用Token验证服务异常,uri:{}，token:{},IP:{}", uri, token, ip, e);
            output(response, ErrorCodeEnum.SERVER_ERROR.getCode(), ErrorCodeEnum.SERVER_ERROR.getMessage());
            return false;
        }

        return true;
    }

    private SSOUser buildLoginUserByToken(HttpServletResponse response, String token) {
        if (StringUtils.isBlank(token)) {
            log.error("token缺失");
            //output(response, ErrorCodeEnum.ERROR_TOKEN_EXPIRE.getCode(), ErrorCodeEnum.ERROR_TOKEN_EXPIRE.getMessage());
            throw new ApiException(ErrorCodeEnum.ERROR_TOKEN_EXPIRE);
        }

        String value = stringRedisTemplate.opsForValue().get(token);
        if (StringUtils.isBlank(value)) {
            log.error("token失效");
            //output(response, ErrorCodeEnum.ERROR_TOKEN_EXPIRE.getCode(), ErrorCodeEnum.ERROR_TOKEN_EXPIRE.getMessage());
            throw new ApiException(ErrorCodeEnum.ERROR_TOKEN_EXPIRE);
        }

        // token更新频率，设置离过期时间还剩n秒以内才更新一次token
        Long time = stringRedisTemplate.getExpire(token);
        if (time != null && time.intValue() <= Constants.LOGIN_TOKEN_REFRESH_TIME.intValue()) {
            stringRedisTemplate.opsForValue().set(token, value);
            stringRedisTemplate.expire(token, Constants.LOGIN_TOKEN_TIME.intValue(), TimeUnit.SECONDS);
        }

        return JSONObject.parseObject(value, SSOUser.class);
    }

    private void output(HttpServletResponse response, String errCode, String errMsg) {
        ResponseEntityDto ret = ResponseEntityBuilder.buildErrorResponse(errCode, errMsg);
        try {
            log.info("token验证未通过,{\"errCode\":" + errCode + ",\"errMsg:\":" + errMsg + "}");
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Type", "application/json;charset=UTF-8");
            byte[] jsonBytes = JSON.toJSONBytes(ret);
            OutputStream output = response.getOutputStream();
            output.write(jsonBytes);
            output.flush();
        } catch (IOException e) {
            log.error("输出响应报文异常！,{},{}", errCode, errMsg, e);
        }
    }

    private SSOUser mockLoginUser() {
        if (mockEnable) {
            SSOUser mockLoginUser = new SSOUser();
            mockLoginUser.setId(999);
            mockLoginUser.setMobile("123xxxx9999");
            mockLoginUser.setName("mock用户");
            return mockLoginUser;
        }
        return null;
    }
}